package com.ty.user.starter.advice;

import cn.hutool.core.convert.Convert;
import com.ty.user.starter.annon.Encrypt;
import com.ty.user.starter.exception.UserMgtException;
import com.ty.user.starter.util.R;
import com.ty.user.starter.util.RSAUtil;
import com.ty.user.starter.util.SessionUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.PropertyUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import java.lang.reflect.InvocationTargetException;
import java.security.KeyPair;
import java.util.Objects;

/**
 * 数据返回处理器
 * 只对{@link com.ty.user.starter.annon.Encrypt}注解进行加密操作
 * 注意,此处理器只对{@link org.springframework.web.bind.annotation.ResponseBody}注解有效
 */

@Slf4j
@RestControllerAdvice
public class EncryptResponseBodyAdvice implements ResponseBodyAdvice<Object> {
    @Override
    public boolean supports(MethodParameter methodParameter, @NotNull Class aClass) {
        return Objects.requireNonNull(methodParameter.getMethod()).isAnnotationPresent(Encrypt.class);
    }

    @Override
    public Object beforeBodyWrite(Object o, MethodParameter methodParameter, @NotNull MediaType mediaType, @NotNull Class aClass, @NotNull ServerHttpRequest serverHttpRequest, @NotNull ServerHttpResponse serverHttpResponse) {
        Encrypt encrypt = Objects.requireNonNull(methodParameter.getMethod()).getAnnotation(Encrypt.class);
        String[] fields = encrypt.field();
        KeyPair keyPair = (KeyPair) SessionUtils.getSession().getAttribute(SessionUtils.RSA_KEY);
        Assert.notNull(keyPair, "请先获取公钥");
        for (String field : fields) {
            try {
                R<?> r = Convert.convert(R.class, o);
                Object data = r.getData();
                Object property = PropertyUtils.getProperty(data, field);
                Assert.isTrue(property instanceof String, "加密字段必须为字符串类型");
                // TODO 字符串加密,此处目前只做显示,用了后端的公钥.正常情况下应该是用前端项目的公钥来进行加密
                byte[] bytes = RSAUtil.encrypt(((String) property).getBytes(), keyPair.getPublic());
                PropertyUtils.setProperty(data, field, RSAUtil.encodeBase64(bytes));
            } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException | UserMgtException e) {
                log.error("字符加密失败", e);
                throw new IllegalArgumentException(String.format("属性: %s 加密失败", field));
            }
        }
        return o;
    }
}
